///////////////////////////////////////////////////////////////////////////////
//
//                          IMPORTANT NOTICE
//
// The following open source license statement does not apply to any
// entity in the Exception List published by FMSoft.
//
// For more information, please visit:
//
// https://www.fmsoft.cn/exception-list
//
//////////////////////////////////////////////////////////////////////////////
/*
 *   This file is part of MiniGUI, a mature cross-platform windowing
 *   and Graphics User Interface (GUI) support system for embedded systems
 *   and smart IoT devices.
 *
 *   Copyright (C) 2002~2019, Beijing FMSoft Technologies Co., Ltd.
 *   Copyright (C) 1998~2002, WEI Yongming
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *   Or,
 *
 *   As this program is a library, any link to this program must follow
 *   GNU General Public License version 3 (GPLv3). If you cannot accept
 *   GPLv3, you need to be licensed from FMSoft.
 *
 *   If you have got a commercial license of this program, please use it
 *   under the terms and conditions of the commercial license.
 *
 *   For more information about the commercial license, please refer to
 *   <http://www.minigui.com/blog/minigui-licensing-policy/>.
 */
/*
** random.c: the random IAL Engine.
**
** Created by WEI Yongming, 2005/06/08
**
** Enhanced by WEI Yongming to support extra input events in June, 2019.
**
** The MiniGUI runtime configuration key `random.logfile` specifies
** the log file which will store the input events generated by this engine.
** If MiniGUI failed to open the log file, the log feature will be disabled.
**
** The MiniGUI runtime configuration key `random.eventtypes` specifies
** the input event types which will be generated by this IAL engine,
** in the following pattern:
**
**  <event-type>[-<event-type>]*
**
** The <event-type> can be one of the following values:
**
**  - mouse: mouse.
**  - keyboard: keyboard.
**  - button: buttons.
**  - single_touch: touch pen or single touch panel.
**  - multi_touch: multiple touch panel.
**  - gesture: gesture.
**  - tablet_tool: tablet tool.
**  - tablet_pad: tablet pad.
**  - switch: switch.
**
** The MiniGUI ETC key `random.minkeycode` specifies the minimal key code
** which can be generated by the engine if `keyboard` is included.
**
** The MiniGUI ETC key `random.maxkeycode` specifies the maximal key code
** which can be generated by the engine if `keyboard` is included.
**
** The MiniGUI ETC key `random.minbtncode` specifies the minimal button code
** which can be generated by the engine if `button` is included.
**
** The MiniGUI ETC key `random.maxbtncode` specifies the maximal key code
** which can be generated by the engine if `button` is included.
**
** For invalid `random.eventtyps`, use `mouse` as default.
**
** For invalid `random.minkeycode`, and/or `random.maxkeycode key values, use
** `SCANCODE_ESCAPE`, and `SCANCODE_MICMUTE` respectively.
**
** For invalid `random.minbtncode`, and/or `random.maxbtncode key values, use
** `0x100` (BTN_MISC defined by Linux kernel), and `0x2ff` (KEY_MAX defined by
** Linux kernel) respectively.
**
** This engine maintains a state machine for each input event type, and
** generates a reasonable event sequence for each type. If and only if
** an event sequence finished or cancelled, the engine switch to another
** event type randomly.
**
** Note that currently, the following event types are not implemented:
**
**  - multi_touch
**  - tablet_tool
**  - tablet_pad
**  - switch
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//#define _DEBUG
#include "common.h"

#ifdef _MGIAL_RANDOM

#include <time.h>
#include <sys/time.h>

#include "minigui.h"
#include "misc.h"
#include "ial.h"
#include "random.h"

#define BTN_NONE            0

#ifdef __LINUX__
#include <linux/input-event-codes.h>
#else
#define BTN_LEFT      0x110
#define BTN_RIGHT     0x111
#define BTN_MIDDLE    0x112
#endif

#define MIN_SCANCODE        SCANCODE_ESCAPE
#define MAX_SCANCODE        SCANCODE_MICMUTE

#define MIN_BTNCODE         0x100
#define MAX_BTNCODE         0x2ff

#define MAX_PRESSED_BTNS    10

#define MAX_ABS_X           2048
#define MAX_ABS_Y           2048

enum _random_event_type {
    RANDOM_EVENT_NONE,
    RANDOM_EVENT_WAIT,
    RANDOM_EVENT_MOUSE_MOTION,
    RANDOM_EVENT_MOUSE_BUTTON,
    RANDOM_EVENT_MOUSE_WHEEL,
    RANDOM_EVENT_KEYBOARD_KEY,
    RANDOM_EVENT_BUTTON,
    RANDOM_EVENT_STOUCH_DOWN,
    RANDOM_EVENT_STOUCH_UP,
    RANDOM_EVENT_STOUCH_MOTION,
    RANDOM_EVENT_MTOUCH_DOWN,
    RANDOM_EVENT_MTOUCH_UP,
    RANDOM_EVENT_MTOUCH_MOTION,
    RANDOM_EVENT_MTOUCH_CANCEL,
    RANDOM_EVENT_MTOUCH_FRAME,
    RANDOM_EVENT_GESTURE_SWIPE_BEGIN,
    RANDOM_EVENT_GESTURE_SWIPE_UPDATE,
    RANDOM_EVENT_GESTURE_SWIPE_END,
    RANDOM_EVENT_GESTURE_PINCH_BEGIN,
    RANDOM_EVENT_GESTURE_PINCH_UPDATE,
    RANDOM_EVENT_GESTURE_PINCH_END,
};

struct _random_event {
    enum _random_event_type type;
};

struct _event_state_machine;

#define NR_EVENT_TYPES      10

struct _random_input_context {
    FILE* log_fp;
    int min_x, max_x, min_y, max_y;
    int mouse_x, mouse_y, mouse_button;
    int min_keycode, max_keycode;
    int min_btncode, max_btncode;

    int last_keycode;
    char* kbd_state;

    struct _event_state_machine *esm [NR_EVENT_TYPES];
    int nr_machines;
    int curr_machine;
};

typedef void* (*op_start_event_machine)(const struct _random_input_context* ctxt);
typedef void (*op_stop_event_machine)(void* machine);
typedef void (*op_resume_event_machine)(void* machine);
typedef const struct _random_event* (*op_generate_event)(void* machine);

enum _button_state {
    BUTTON_STATE_RELEASED,
    BUTTON_STATE_PRESSED,
};

enum _key_state {
    KEY_STATE_RELEASED,
    KEY_STATE_PRESSED,
};

/* implementation of state machine for mouse */
struct _mouse_event {
    struct _random_event event;
    uint32_t            button;
    enum _button_state  state;
    int                 dx, dy;
    int                 sv;     // scroll value
    int                 dsv;    // discrete scroll value
};

enum _mouse_op_type {
    MOUSE_OP_IDLE = 0,
    MOUSE_OP_MOVE,
    MOUSE_OP_CLICK,
    MOUSE_OP_PRESSED_MOVE,
    MOUSE_OP_WHEEL,
    MOUSE_OP_COUNT,
};

struct _mouse_event_machine {
    struct _mouse_event event;
    enum _mouse_op_type op_type;
    int                 op_sec;
    time_t              op_start;

    uint32_t            btn_sel;
    uint32_t            btn_down;
};

static void* start_event_machine_mouse(const struct _random_input_context* ctxt)
{
    struct _mouse_event_machine* machine;
    machine = calloc(1, sizeof (struct _mouse_event_machine));

    return machine;
}

static void stop_event_machine_mouse(void* machine)
{
    if (machine)
        free(machine);
}

static void resume_event_machine_mouse(void* machine)
{
    struct _mouse_event_machine* my_machine =
            (struct _mouse_event_machine*)machine;

    my_machine->op_type = MOUSE_OP_IDLE;
    my_machine->op_sec = random() % 5;
    my_machine->op_start = time(NULL);
}

static const struct _random_event* generate_event_mouse(void* machine)
{
    struct _mouse_event_machine* my_machine =
            (struct _mouse_event_machine*)machine;
    time_t now = time(NULL);

    memset(&my_machine->event, 0, sizeof(my_machine->event));

    if ((now - my_machine->op_start) > my_machine->op_sec) {
        my_machine->op_type = now % MOUSE_OP_COUNT;
        my_machine->op_sec = random() % 5 + 1;
        my_machine->op_start = time(NULL);

        switch (now % 5) {
        case 0:
            my_machine->btn_sel = BTN_RIGHT;
            break;
        case 1:
            my_machine->btn_sel = BTN_MIDDLE;
            break;
        default: /* 60% possibility to choose the left button. */
            my_machine->btn_sel = BTN_LEFT;
            break;
        }

        my_machine->event.event.type = RANDOM_EVENT_NONE;
        goto ret;
    }

    /* valid operation */
    if ((GetTickCount() % 5)) {
        // 80% possibility to wait for a new event.
        __mg_os_time_delay(11);
        my_machine->event.event.type = RANDOM_EVENT_WAIT;
        goto ret;
    }

    switch (my_machine->op_type) {
    case MOUSE_OP_IDLE:
        my_machine->event.event.type = RANDOM_EVENT_WAIT;
        break;

    case MOUSE_OP_MOVE:
        my_machine->event.event.type = RANDOM_EVENT_MOUSE_MOTION;
        my_machine->event.dx = (random() % 100) - 50;
        my_machine->event.dy = (random() % 100) - 50;
        break;

    case MOUSE_OP_CLICK:
        if (my_machine->btn_down == BTN_NONE) {
            my_machine->btn_down = my_machine->btn_sel;

            my_machine->event.event.type = RANDOM_EVENT_MOUSE_BUTTON;
            my_machine->event.button = my_machine->btn_down;
            my_machine->event.state = BUTTON_STATE_PRESSED;
        }
        else {
            my_machine->event.event.type = RANDOM_EVENT_MOUSE_BUTTON;
            my_machine->event.button = my_machine->btn_down;
            my_machine->event.state = BUTTON_STATE_RELEASED;

            my_machine->btn_down = BTN_NONE;
        }
        break;

    case MOUSE_OP_PRESSED_MOVE:
        if (my_machine->btn_down == BTN_NONE) {
            my_machine->btn_down = my_machine->btn_sel;

            my_machine->event.event.type = RANDOM_EVENT_MOUSE_BUTTON;
            my_machine->event.button = my_machine->btn_down;
            my_machine->event.state = BUTTON_STATE_PRESSED;
        }
        else if ((GetTickCount() % 5) == 0) {
            my_machine->event.event.type = RANDOM_EVENT_MOUSE_BUTTON;
            my_machine->event.button = my_machine->btn_down;
            my_machine->event.state = BUTTON_STATE_RELEASED;

            my_machine->btn_down = BTN_NONE;
        }
        else {
            my_machine->event.event.type = RANDOM_EVENT_MOUSE_MOTION;
            my_machine->event.dx = (random() % 100) - 50;
            my_machine->event.dy = (random() % 100) - 50;
        }
        break;

    case MOUSE_OP_WHEEL:
        my_machine->event.event.type = RANDOM_EVENT_MOUSE_WHEEL;
        my_machine->event.dsv = (random() % 10) - 5;
        my_machine->event.sv = 15 * my_machine->event.dsv; // 15 degrees per tick
        break;

    default:
        assert(0);
        break;
    }

ret:
    return &my_machine->event.event;
}

static inline const
struct _mouse_event* get_mouse_event(const struct _random_event* event)
{
    if (event->type != RANDOM_EVENT_MOUSE_MOTION &&
            event->type != RANDOM_EVENT_MOUSE_BUTTON &&
            event->type != RANDOM_EVENT_MOUSE_WHEEL)
        return NULL;

    return (struct _mouse_event*)event;
}

/* implementation of state machine for keyboard */
struct _keyboard_event {
    struct _random_event event;
    int                 keycode;
    enum _key_state     state;
};

enum _keyboard_op_type {
    KEYBOARD_OP_IDLE = 0,
    KEYBOARD_OP_NORMAL_KEY,
    KEYBOARD_OP_SHIFT_KEYS,
    KEYBOARD_OP_COUNT,
};

struct _keyboard_event_machine {
    struct _keyboard_event event;
    enum _keyboard_op_type op_type;
    int                 op_sec;
    time_t              op_start;

    int                 min_keycode, max_keycode;
    uint32_t            pressed_shift_keys;
    int                 pressed_normal_key;
};

struct _keyboard_shift_key {
    int keycode;
    uint32_t flag;
} keyboard_shift_keys[] = {
    { SCANCODE_CAPSLOCK, KS_CAPSLOCK },
    { SCANCODE_NUMLOCK, KS_NUMLOCK },
    { SCANCODE_SCROLLLOCK, KS_SCROLLLOCK },
    { SCANCODE_LEFTCTRL, KS_LEFTCTRL },
    { SCANCODE_RIGHTCTRL, KS_RIGHTCTRL },
    { SCANCODE_LEFTALT, KS_LEFTALT },
    { SCANCODE_RIGHTALT, KS_RIGHTALT },
    { SCANCODE_LEFTSHIFT, KS_LEFTSHIFT },
    { SCANCODE_RIGHTSHIFT, KS_RIGHTSHIFT },
    { SCANCODE_LEFTMETA, KS_LEFTMETA },
    { SCANCODE_RIGHTMETA, KS_RIGHTMETA },
};

static void* start_event_machine_keyboard(const struct _random_input_context* ctxt)
{
    struct _keyboard_event_machine* machine;
    machine = calloc(1, sizeof (struct _keyboard_event_machine));

    machine->min_keycode = ctxt->min_keycode;
    machine->max_keycode = ctxt->max_keycode;
    return machine;
}

static void stop_event_machine_keyboard(void* machine)
{
    if (machine)
        free(machine);
}

static void resume_event_machine_keyboard(void* machine)
{
    struct _keyboard_event_machine* my_machine =
            (struct _keyboard_event_machine*)machine;

    my_machine->op_type = KEYBOARD_OP_IDLE;
    my_machine->op_sec = random() % 5;
    my_machine->op_start = time(NULL);
}

static const struct _random_event* generate_event_keyboard(void* machine)
{
    struct _keyboard_event_machine* my_machine =
            (struct _keyboard_event_machine*)machine;
    time_t now = time(NULL);

    memset(&my_machine->event, 0, sizeof(my_machine->event));

    if ((now - my_machine->op_start) > my_machine->op_sec) {
        my_machine->op_type = now % KEYBOARD_OP_COUNT;
        my_machine->op_sec = random() % 5;
        my_machine->op_start = time(NULL);

        my_machine->event.event.type = RANDOM_EVENT_NONE;
        goto ret;
    }

    /* valid operation */
    if ((GetTickCount() % 5)) {
        // 80% possibility to wait for a new event.
        __mg_os_time_delay(11);
        my_machine->event.event.type = RANDOM_EVENT_WAIT;
        goto ret;
    }

    switch (my_machine->op_type) {
    case KEYBOARD_OP_IDLE:
        my_machine->event.event.type = RANDOM_EVENT_WAIT;
        break;

    case KEYBOARD_OP_NORMAL_KEY:
        if (my_machine->pressed_normal_key == SCANCODE_RESERVED) {
            my_machine->pressed_normal_key =
                (random() %
                    (my_machine->max_keycode - my_machine->min_keycode + 1)) +
                    my_machine->min_keycode;

            my_machine->event.event.type = RANDOM_EVENT_KEYBOARD_KEY;
            my_machine->event.keycode = my_machine->pressed_normal_key;
            my_machine->event.state = KEY_STATE_PRESSED;
        }
        else {
            my_machine->event.event.type = RANDOM_EVENT_KEYBOARD_KEY;
            my_machine->event.keycode = my_machine->pressed_normal_key;
            my_machine->event.state = KEY_STATE_RELEASED;

            my_machine->pressed_normal_key = SCANCODE_RESERVED;
        }
        break;

    case KEYBOARD_OP_SHIFT_KEYS: {
        int i = random() % (TABLESIZE(keyboard_shift_keys));

        if (keyboard_shift_keys[i].flag & my_machine->pressed_shift_keys) {
            my_machine->event.event.type = RANDOM_EVENT_KEYBOARD_KEY;
            my_machine->event.keycode = keyboard_shift_keys[i].keycode;
            my_machine->event.state = KEY_STATE_RELEASED;

            my_machine->pressed_shift_keys &= ~keyboard_shift_keys[i].flag;
        }
        else {
            my_machine->event.event.type = RANDOM_EVENT_KEYBOARD_KEY;
            my_machine->event.keycode = keyboard_shift_keys[i].keycode;
            my_machine->event.state = KEY_STATE_PRESSED;

            my_machine->pressed_shift_keys |= keyboard_shift_keys[i].flag;
        }
        break;
    }

    default:
        assert(0);
        break;
    }

ret:
    return &my_machine->event.event;
}

static inline const
struct _keyboard_event* get_keyboard_event(const struct _random_event* event)
{
    if (event->type != RANDOM_EVENT_KEYBOARD_KEY)
        return NULL;

    return (struct _keyboard_event*)event;
}

/* implementation of state machine for button */
struct _button_event {
    struct _random_event event;
    int                 btncode;
    enum _button_state  state;
    int                 nr_down;
};

enum _button_op_type {
    BUTTON_OP_IDLE = 0,
    BUTTON_OP_NORMAL,
    BUTTON_OP_COUNT,
};

struct _button_event_machine {
    struct _button_event event;
    enum _button_op_type op_type;
    int                 op_sec;
    time_t              op_start;

    int                 min_btncode, max_btncode;
    int                 last_btn;
    int                 pressed_btns[MAX_PRESSED_BTNS];
};

static inline
int button_pressed_count(struct _button_event_machine* machine)
{
    int i, n = 0;

    for (i = 0; i < MAX_PRESSED_BTNS; i++) {
        if (machine->pressed_btns[i]) {
            n++;
        }
    }

    return n;
}

static inline
int button_is_pressed(struct _button_event_machine* machine, int btn)
{
    int i;

    for (i = 0; i < MAX_PRESSED_BTNS; i++) {
        if (btn == machine->pressed_btns[i]) {
            return i;
        }
    }

    return -1;
}

static inline
int button_clear(struct _button_event_machine* machine, int btn)
{
    int i;

    for (i = 0; i < MAX_PRESSED_BTNS; i++) {
        if (btn == machine->pressed_btns[i]) {
            machine->pressed_btns[i] = BTN_NONE;
            return i;
        }
    }

    return -1;
}

static inline
int button_set(struct _button_event_machine* machine, int btn)
{
    int i;

    for (i = 0; i < MAX_PRESSED_BTNS; i++) {
        if (machine->pressed_btns[i] == BTN_NONE) {
            machine->pressed_btns[i] = btn;
            return i;
        }
    }

    return -1;
}

static void* start_event_machine_button(const struct _random_input_context* ctxt)
{
    struct _button_event_machine* machine;
    machine = calloc(1, sizeof (struct _button_event_machine));

    machine->min_btncode = ctxt->min_btncode;
    machine->max_btncode = ctxt->max_btncode;
    return machine;
}

static void stop_event_machine_button(void* machine)
{
    if (machine)
        free(machine);
}

static void resume_event_machine_button(void* machine)
{
    struct _button_event_machine* my_machine =
            (struct _button_event_machine*)machine;

    my_machine->op_type = BUTTON_OP_IDLE;
    my_machine->op_sec = random() % 5;
    my_machine->op_start = time(NULL);
}

static const struct _random_event* generate_event_button(void* machine)
{
    struct _button_event_machine* my_machine =
            (struct _button_event_machine*)machine;
    time_t now = time(NULL);

    memset(&my_machine->event, 0, sizeof(my_machine->event));

    if ((now - my_machine->op_start) > my_machine->op_sec) {
        my_machine->op_type = now % BUTTON_OP_COUNT;
        my_machine->op_sec = random() % 5;
        my_machine->op_start = time(NULL);

        my_machine->event.event.type = RANDOM_EVENT_NONE;
        goto ret;
    }

    /* valid operation */
    if ((GetTickCount() % 5)) {
        // 80% possibility to wait for a new event.
        __mg_os_time_delay(11);
        my_machine->event.event.type = RANDOM_EVENT_WAIT;
        goto ret;
    }

    switch (my_machine->op_type) {
    case BUTTON_OP_IDLE:
        my_machine->event.event.type = RANDOM_EVENT_WAIT;
        break;

    case BUTTON_OP_NORMAL: {
        if (my_machine->last_btn != BTN_NONE && (GetTickCount() % 5)) {
            // 80% possibility to release last button.
            int idx = button_clear(my_machine, my_machine->last_btn);

            assert (idx >= 0);
            my_machine->event.event.type = RANDOM_EVENT_BUTTON;
            my_machine->event.btncode = my_machine->last_btn;
            my_machine->event.state = BUTTON_STATE_RELEASED;

            my_machine->last_btn = BTN_NONE;
        }
        else {
            int btn =
                    (random() %
                        (my_machine->max_btncode - my_machine->min_btncode + 1)) +
                    my_machine->min_btncode;
            int idx = button_is_pressed(my_machine, btn);
            if (idx < 0) {
                idx = button_set(my_machine, btn);
                if (idx < 0) {
                    // full pressed buttons; release a random button
                    idx = random() % MAX_PRESSED_BTNS;

                    my_machine->event.event.type = RANDOM_EVENT_BUTTON;
                    my_machine->event.btncode = my_machine->pressed_btns[idx];
                    my_machine->event.state = BUTTON_STATE_RELEASED;

                    my_machine->last_btn = BTN_NONE;
                    my_machine->pressed_btns[idx] = BTN_NONE;
                }
                else {
                    my_machine->event.event.type = RANDOM_EVENT_BUTTON;
                    my_machine->event.btncode = btn;
                    my_machine->event.state = BUTTON_STATE_PRESSED;

                    my_machine->last_btn = btn;
                }
            }
            else {
                my_machine->event.event.type = RANDOM_EVENT_BUTTON;
                my_machine->event.btncode = my_machine->pressed_btns[idx];
                my_machine->event.state = BUTTON_STATE_RELEASED;

                my_machine->last_btn = BTN_NONE;
                my_machine->pressed_btns[idx] = BTN_NONE;
            }
        }

        my_machine->event.nr_down = button_pressed_count(my_machine);
        break;
    }

    default:
        assert(0);
        break;
    }

ret:
    return &my_machine->event.event;
}

static inline const
struct _button_event* get_button_event(const struct _random_event* event)
{
    if (event->type != RANDOM_EVENT_BUTTON)
        return NULL;

    return (struct _button_event*)event;
}

/* implementation of state machine for stouch */
struct _stouch_event {
    struct _random_event event;
    enum _button_state  state;
    int                 x, y;
};

enum _stouch_op_type {
    STOUCH_OP_IDLE = 0,
    STOUCH_OP_DOWN,
    STOUCH_OP_DOWN_MOVE,
    STOUCH_OP_COUNT,
};

struct _stouch_event_machine {
    struct _stouch_event event;
    enum _stouch_op_type op_type;
    int                 op_sec;
    time_t              op_start;

    int                 down;
};

static void* start_event_machine_stouch(const struct _random_input_context* ctxt)
{
    struct _stouch_event_machine* machine;
    machine = calloc(1, sizeof (struct _stouch_event_machine));

    return machine;
}

static void stop_event_machine_stouch(void* machine)
{
    if (machine)
        free(machine);
}

static void resume_event_machine_stouch(void* machine)
{
    struct _stouch_event_machine* my_machine =
            (struct _stouch_event_machine*)machine;

    my_machine->op_type = STOUCH_OP_IDLE;
    my_machine->op_sec = random() % 5;
    my_machine->op_start = time(NULL);
}

static const struct _random_event* generate_event_stouch(void* machine)
{
    struct _stouch_event_machine* my_machine =
            (struct _stouch_event_machine*)machine;
    time_t now = time(NULL);

    memset(&my_machine->event, 0, sizeof(my_machine->event));

    if ((now - my_machine->op_start) > my_machine->op_sec) {
        my_machine->op_type = now % STOUCH_OP_COUNT;
        my_machine->op_sec = random() % 5 + 1;
        my_machine->op_start = time(NULL);
        my_machine->event.event.type = RANDOM_EVENT_NONE;
        goto ret;
    }

    /* valid operation */
    if ((GetTickCount() % 5)) {
        // 80% possibility to wait for a new event.
        __mg_os_time_delay(11);
        my_machine->event.event.type = RANDOM_EVENT_WAIT;
        goto ret;
    }

    switch (my_machine->op_type) {
    case STOUCH_OP_IDLE:
        my_machine->event.event.type = RANDOM_EVENT_WAIT;
        break;

    case STOUCH_OP_DOWN:
        if (my_machine->down) {
            my_machine->event.event.type = RANDOM_EVENT_STOUCH_DOWN;
            my_machine->event.state = BUTTON_STATE_RELEASED;
            my_machine->down = 0;
        }
        else {
            my_machine->event.event.type = RANDOM_EVENT_STOUCH_DOWN;
            my_machine->event.state = BUTTON_STATE_PRESSED;
            my_machine->event.x = random() % MAX_ABS_X;
            my_machine->event.y = random() % MAX_ABS_Y;

            my_machine->down = 1;
        }
        break;

    case STOUCH_OP_DOWN_MOVE:
        if (my_machine->down == 0) {
            my_machine->down = 1;

            my_machine->event.event.type = RANDOM_EVENT_STOUCH_DOWN;
            my_machine->event.state = BUTTON_STATE_PRESSED;

            my_machine->event.x = random() % MAX_ABS_X;
            my_machine->event.y = random() % MAX_ABS_Y;
        }
        else if ((GetTickCount() % 5) == 0) {
            my_machine->event.event.type = RANDOM_EVENT_STOUCH_DOWN;
            my_machine->event.state = BUTTON_STATE_RELEASED;

            my_machine->down = 0;
        }
        else {
            my_machine->event.event.type = RANDOM_EVENT_STOUCH_MOTION;

            my_machine->event.x += (random() % 10) - 5;
            my_machine->event.y += (random() % 10) - 5;
        }
        break;

    default:
        assert(0);
        break;
    }

ret:
    return &my_machine->event.event;
}

static inline const
struct _stouch_event* get_stouch_event(const struct _random_event* event)
{
    if (event->type != RANDOM_EVENT_STOUCH_MOTION &&
            event->type != RANDOM_EVENT_STOUCH_DOWN &&
            event->type != RANDOM_EVENT_STOUCH_UP)
        return NULL;

    return (struct _stouch_event*)event;
}

/* implementation of state machine for gesture */
struct _gesture_event {
    struct _random_event event;
    int                 nr_figs;
    int                 dx, dy;
    int                 is_cancelled;

    int                 scale;
    int                 da;
};

enum _gesture_op_type {
    GESTURE_OP_IDLE = 0,
    GESTURE_OP_SWIPE,
    GESTURE_OP_PINCH,
    GESTURE_OP_COUNT,
};

struct _gesture_event_machine {
    struct _gesture_event event;
    enum _gesture_op_type op_type;
    int                 op_sec;
    time_t              op_start;

    int                 started;
};

static void* start_event_machine_gesture(const struct _random_input_context* ctxt)
{
    struct _gesture_event_machine* machine;
    machine = calloc(1, sizeof (struct _gesture_event_machine));

    return machine;
}

static void stop_event_machine_gesture(void* machine)
{
    if (machine)
        free(machine);
}

static void resume_event_machine_gesture(void* machine)
{
    struct _gesture_event_machine* my_machine =
            (struct _gesture_event_machine*)machine;

    my_machine->op_type = GESTURE_OP_IDLE;
    my_machine->op_sec = random() % 5;
    my_machine->op_start = time(NULL);
}

static const struct _random_event* generate_event_gesture(void* machine)
{
    struct _gesture_event_machine* my_machine =
            (struct _gesture_event_machine*)machine;
    time_t now = time(NULL);

    memset(&my_machine->event, 0, sizeof(my_machine->event));

    if ((now - my_machine->op_start) > my_machine->op_sec &&
            my_machine->started == 0) {
        my_machine->op_type = now % GESTURE_OP_COUNT;
        my_machine->op_sec = random() % 5 + 1;
        my_machine->op_start = time(NULL);
        my_machine->event.event.type = RANDOM_EVENT_NONE;

        memset(&my_machine->event, 0, sizeof(struct _gesture_event));
        goto ret;
    }

    /* valid operation */
    if ((GetTickCount() % 2)) {
        // 50% possibility to wait for a new event.
        __mg_os_time_delay(11);
        my_machine->event.event.type = RANDOM_EVENT_WAIT;
        goto ret;
    }

    switch (my_machine->op_type) {
    case GESTURE_OP_IDLE:
        my_machine->event.event.type = RANDOM_EVENT_WAIT;
        break;

    case GESTURE_OP_SWIPE:
        if (my_machine->started == 0) {
            my_machine->event.event.type = RANDOM_EVENT_GESTURE_SWIPE_BEGIN;
            my_machine->event.nr_figs = random() % 5 + 1;
            my_machine->started = 1;
        }
        else if (GetTickCount() % 10) {
            // 90% possibility to wait update
            my_machine->event.event.type = RANDOM_EVENT_GESTURE_SWIPE_UPDATE;
            my_machine->event.dx = (random() % 100) - 50;
            my_machine->event.dy = (random() % 100) - 50;
        }
        else {
            my_machine->event.event.type = RANDOM_EVENT_GESTURE_SWIPE_END;
            if (GetTickCount() % 3)
                my_machine->event.is_cancelled = 0;
            else
                my_machine->event.is_cancelled = 1;

            my_machine->started = 0;
        }
        break;

    case GESTURE_OP_PINCH:
        if (my_machine->started == 0) {
            my_machine->event.event.type = RANDOM_EVENT_GESTURE_PINCH_BEGIN;
            my_machine->event.nr_figs = random() % 5 + 1;
            my_machine->event.scale = 100;
            my_machine->started = 1;
        }
        else if (GetTickCount() % 10) {
            // 90% possibility to wait update
            my_machine->event.event.type = RANDOM_EVENT_GESTURE_PINCH_UPDATE;
            if (GetTickCount() % 2)
                my_machine->event.scale *= (random() % 5) + 1;
            else
                my_machine->event.scale /= (random() % 5) + 1;
            if (my_machine->event.scale == 0)
                my_machine->event.scale = 1;

            my_machine->event.da = (random() % 10) * 25;
            my_machine->event.dx = (random() % 100) - 50;
            my_machine->event.dy = (random() % 100) - 50;
        }
        else {
            my_machine->event.event.type = RANDOM_EVENT_GESTURE_PINCH_END;
            my_machine->event.scale = (random() % 1000) + 1;
            if (my_machine->event.scale == 0)
                my_machine->event.scale = 1;

            if (GetTickCount() % 3)
                my_machine->event.is_cancelled = 0;
            else
                my_machine->event.is_cancelled = 1;

            my_machine->started = 0;
        }
        break;

    default:
        assert(0);
        break;
    }

ret:
    return &my_machine->event.event;
}

static inline const
struct _gesture_event* get_gesture_event(const struct _random_event* event)
{
    if (event->type < RANDOM_EVENT_GESTURE_SWIPE_BEGIN ||
            event->type > RANDOM_EVENT_GESTURE_PINCH_END)
        return NULL;

    return (struct _gesture_event*)event;
}

static struct _event_state_machine {
    const char* name;
    op_start_event_machine start_machine;
    op_resume_event_machine resume_machine;
    op_generate_event generate_event;
    op_stop_event_machine stop_machine;
    void* machine;
} event_state_machines [] = {
    { "mouse",
        start_event_machine_mouse,
        resume_event_machine_mouse,
        generate_event_mouse,
        stop_event_machine_mouse,
    },
    { "keyboard",
        start_event_machine_keyboard,
        resume_event_machine_keyboard,
        generate_event_keyboard,
        stop_event_machine_keyboard,
    },
    { "button",
        start_event_machine_button,
        resume_event_machine_button,
        generate_event_button,
        stop_event_machine_button,
    },
    { "single_touch",
        start_event_machine_stouch,
        resume_event_machine_stouch,
        generate_event_stouch,
        stop_event_machine_stouch,
    },
    { "multi_touch",
    },
    { "gesture",
        start_event_machine_gesture,
        resume_event_machine_gesture,
        generate_event_gesture,
        stop_event_machine_gesture,
    },
    { "tablet_tool",
    },
    { "tablet_pad",
    },
    { "switch",
    },
};

static struct _random_input_context my_ctxt;

static void mouse_setrange (int newminx, int newminy, int newmaxx, int newmaxy)
{
    my_ctxt.min_x = newminx;
    my_ctxt.max_x = newmaxx;
    my_ctxt.min_y = newminy;
    my_ctxt.max_y = newmaxy;

    my_ctxt.mouse_x = (my_ctxt.min_x + my_ctxt.max_x) / 2;
    my_ctxt.mouse_y = (my_ctxt.min_y + my_ctxt.max_y) / 2;
}

static void mouse_setxy (int newx, int newy)
{
    if (newx < my_ctxt.min_x)
        newx = my_ctxt.min_x;
    if (newx > my_ctxt.max_x)
        newx = my_ctxt.max_x;
    if (newy < my_ctxt.min_y)
        newy = my_ctxt.min_y;
    if (newy > my_ctxt.max_y)
        newy = my_ctxt.max_y;

    if (newx == my_ctxt.mouse_x && newy == my_ctxt.mouse_y)
        return;

    my_ctxt.mouse_x = newx;
    my_ctxt.mouse_x = newy;
}

static int mouse_update(void)
{
    return 1;
}

static void mouse_getxy (int* x, int* y)
{
    *x = my_ctxt.mouse_x;
    *y = my_ctxt.mouse_y;
}

static int mouse_getbutton(void)
{
    return my_ctxt.mouse_button;
}

static int keyboard_update(void)
{
    if (my_ctxt.last_keycode == 0)
        return 0;

    return my_ctxt.last_keycode + 1;
}

static const char* keyboard_getstate(void)
{
    return my_ctxt.kbd_state;
}

static void normalize_mouse_pos(int* new_x, int* new_y)
{
    if (*new_x < my_ctxt.min_x)
        *new_x = my_ctxt.min_x;
    if (*new_x > my_ctxt.max_x)
        *new_x = my_ctxt.max_x;
    if (*new_y < my_ctxt.min_y)
        *new_y = my_ctxt.min_y;
    if (*new_y > my_ctxt.max_y)
        *new_y = my_ctxt.max_y;
}

static BOOL on_mouse_moved (int dx, int dy)
{
    int new_x = my_ctxt.mouse_x + dx;
    int new_y = my_ctxt.mouse_y + dy;

    normalize_mouse_pos(&new_x, &new_y);
    if (new_x == my_ctxt.mouse_x && new_y == my_ctxt.mouse_y)
        return FALSE;

    _DBG_PRINTF("%s: new mouse position: %d, %d (old: %d, %d; delta: %d, %d)\n",
            __FUNCTION__, new_x, new_y, my_ctxt.mouse_x, my_ctxt.mouse_y, dx, dy);

    my_ctxt.mouse_x = new_x;
    my_ctxt.mouse_y = new_y;
    return TRUE;
}

static BOOL on_new_mouse_pos (int x, int y)
{
    int new_x = x;
    int new_y = y;

    normalize_mouse_pos(&new_x, &new_y);
    if (new_x == my_ctxt.mouse_x && new_y == my_ctxt.mouse_y)
        return FALSE;

    _DBG_PRINTF("%s: new mouse position: %d, %d (old: %d, %d)\n",
            __FUNCTION__, new_x, new_y, my_ctxt.mouse_x, my_ctxt.mouse_y);

    my_ctxt.mouse_x = new_x;
    my_ctxt.mouse_y = new_y;
    return TRUE;
}

static BOOL on_mouse_button_changed (uint32_t button,
        enum _button_state state)
{
    int old_mouse_button = my_ctxt.mouse_button;

    switch (button) {
    case BTN_LEFT:
        if (state == BUTTON_STATE_RELEASED)
            my_ctxt.mouse_button &= ~IAL_MOUSE_LEFTBUTTON;
        else
            my_ctxt.mouse_button |= IAL_MOUSE_LEFTBUTTON;
        break;

    case BTN_RIGHT:
        if (state == BUTTON_STATE_RELEASED)
            my_ctxt.mouse_button &= ~IAL_MOUSE_RIGHTBUTTON;
        else
            my_ctxt.mouse_button |= IAL_MOUSE_RIGHTBUTTON;
        break;

    case BTN_MIDDLE:
        if (state == BUTTON_STATE_RELEASED)
            my_ctxt.mouse_button &= ~IAL_MOUSE_MIDDLEBUTTON;
        else
            my_ctxt.mouse_button |= IAL_MOUSE_MIDDLEBUTTON;
        break;

    default:
        // not a standard mouse button.
        return FALSE;
    }

    _DBG_PRINTF("%s: new mouse button: 0x%x (old: 0x%x)\n",
            __FUNCTION__, my_ctxt.mouse_button, old_mouse_button);
    if (old_mouse_button == my_ctxt.mouse_button)
        return FALSE;

    return TRUE;
}

static const
struct _random_event* get_random_event(struct _random_input_context* ctxt)
{
    const struct _random_event* event;

    event = ctxt->esm[ctxt->curr_machine]->generate_event(
            ctxt->esm[ctxt->curr_machine]->machine);

    switch (event->type) {
    case RANDOM_EVENT_NONE:
        ctxt->curr_machine = random() % ctxt->nr_machines;
        return NULL;

    case RANDOM_EVENT_WAIT:
        return NULL;

    default:
        break;
    }

    return event;
}

static inline
int abs_pos_transformed(int x, int w)
{
    return (x * 2000)/w;
}

#define LOG_EVENT(fmt, ...)                             \
    if (my_ctxt.log_fp) {                               \
        fprintf(my_ctxt.log_fp, fmt, ##__VA_ARGS__);    \
        fflush(my_ctxt.log_fp);                         \
    }

static int wait_event_ex (int maxfd, fd_set *in, fd_set *out, fd_set *except,
                struct timeval *timeout, EXTRA_INPUT_EVENT* extra)
{
    const struct _random_event *event;
    int retval;

    event = get_random_event(&my_ctxt);
    if (event == NULL) {
        retval = select (maxfd + 1, in, out, except, timeout);
        if (retval >= 0) {
            return 0;
        }
        else {
            return -1;
        }
    }

    /* set default return value */
    retval = -1;
    switch (event->type) {
    case RANDOM_EVENT_MOUSE_MOTION: {
        const struct _mouse_event* mouse_event = get_mouse_event(event);
        if (mouse_event) {
            if (on_mouse_moved(mouse_event->dx, mouse_event->dy)) {
                LOG_EVENT("%s: dx (%d), dy (%d)\n",
                        "RANDOM_EVENT_MOUSE_MOTION",
                        mouse_event->dx, mouse_event->dy);
            }
        }
        break;
    }

    case RANDOM_EVENT_MOUSE_BUTTON: {
        const struct _mouse_event* mouse_event = get_mouse_event(event);
        if (mouse_event) {
            if (on_mouse_button_changed(mouse_event->button, mouse_event->state)) {
                LOG_EVENT("%s: %u (%s)\n",
                    "RANDOM_EVENT_MOUSE_BUTTON",
                    mouse_event->button,
                    (mouse_event->state == BUTTON_STATE_PRESSED) ? "PRESSED" : "RELEASED");
                retval = IAL_EVENT_MOUSE;
            }
        }
        break;
    }

    case RANDOM_EVENT_MOUSE_WHEEL: {
        const struct _mouse_event* mouse_event = get_mouse_event(event);
        if (mouse_event) {
            extra->event = IAL_EVENT_AXIS;
            if (GetTickCount()%2)
                extra->wparam = MAKELONG(AXIS_SCROLL_VERTICAL, AXIS_SOURCE_WHEEL);
            else
                extra->wparam = MAKELONG(AXIS_SCROLL_HORIZONTAL, AXIS_SOURCE_WHEEL);
            extra->lparam = MAKELONG(mouse_event->sv, mouse_event->dsv);
            retval = IAL_EVENT_EXTRA;
        }
        break;
    }

    case RANDOM_EVENT_KEYBOARD_KEY: {
        const struct _keyboard_event* kbd_event;

        kbd_event = get_keyboard_event(event);
        if (kbd_event) {
            my_ctxt.last_keycode = kbd_event->keycode;
            if (my_ctxt.last_keycode) {
                switch (kbd_event->state) {
                case KEY_STATE_RELEASED:
                    my_ctxt.kbd_state[my_ctxt.last_keycode] = 0;
                    break;
                case KEY_STATE_PRESSED:
                    my_ctxt.kbd_state[my_ctxt.last_keycode] = 1;
                    break;
                }

                LOG_EVENT("%s: %u (%s)\n",
                    "RANDOM_EVENT_KEYBOARD_KEY",
                    kbd_event->keycode,
                    (kbd_event->state == KEY_STATE_PRESSED) ? "PRESSED" : "RELEASED");
                retval = IAL_EVENT_KEY;
            }
        }
        break;
    }

    case RANDOM_EVENT_BUTTON: {
        const struct _button_event* btn_event;

        btn_event = get_button_event(event);
        if (btn_event) {
            retval = 0;
            if (btn_event->btncode >= BTN_LEFT &&
                    btn_event->btncode <= BTN_MIDDLE) {
                if (on_mouse_button_changed(btn_event->btncode, btn_event->state))
                    retval |= IAL_EVENT_MOUSE;
            }

            if (btn_event->state == BUTTON_STATE_PRESSED)
                extra->event = IAL_EVENT_BUTTONDOWN;
            else
                extra->event = IAL_EVENT_BUTTONUP;
            extra->wparam = btn_event->btncode;
            extra->lparam = btn_event->nr_down;
            retval |= IAL_EVENT_EXTRA;
        }
        break;
    }

    case RANDOM_EVENT_STOUCH_DOWN: {
        const struct _stouch_event* tch_event;

        tch_event = get_stouch_event(event);
        if (tch_event) {
            int x, y;
            x = abs_pos_transformed(tch_event->x,
                    my_ctxt.max_x - my_ctxt.min_x + 1) + my_ctxt.min_x;
            y = abs_pos_transformed(tch_event->x,
                    my_ctxt.max_y - my_ctxt.min_y + 1) + my_ctxt.min_y;

            retval = 0;
            // emulate the mouse left button down and mouse move events
            if (on_mouse_button_changed(BTN_LEFT, BUTTON_STATE_PRESSED) ||
                    on_new_mouse_pos(x, y))
                retval |= IAL_EVENT_MOUSE;

            extra->event = IAL_EVENT_TOUCH_DOWN;
            extra->wparam = 0;
            extra->lparam = MAKELONG(my_ctxt.mouse_x, my_ctxt.mouse_y);
            retval |= IAL_EVENT_EXTRA;
        }
        break;
    }

    case RANDOM_EVENT_STOUCH_UP: {
        const struct _stouch_event* tch_event;

        tch_event = get_stouch_event(event);
        if (tch_event) {
            retval = 0;
            // emulate the mouse left button up and mouse move events
            if (on_mouse_button_changed(BTN_LEFT, BUTTON_STATE_RELEASED))
                retval |= IAL_EVENT_MOUSE;

            extra->event = IAL_EVENT_TOUCH_UP;
            extra->wparam = 0;
            extra->lparam = 0;
            retval |= IAL_EVENT_EXTRA;
        }
        break;
    }

    case RANDOM_EVENT_STOUCH_MOTION: {
        const struct _stouch_event* tch_event;

        tch_event = get_stouch_event(event);
        if (tch_event) {
            int x, y;
            x = abs_pos_transformed(tch_event->x,
                    my_ctxt.max_x - my_ctxt.min_x + 1) + my_ctxt.min_x;
            y = abs_pos_transformed(tch_event->x,
                    my_ctxt.max_y - my_ctxt.min_y + 1) + my_ctxt.min_y;

            retval = 0;
            // emulate the mouse move event
            if (on_new_mouse_pos(x, y))
                retval |= IAL_EVENT_MOUSE;

            extra->event = IAL_EVENT_TOUCH_MOTION;
            extra->wparam = 0;
            extra->lparam = MAKELONG(my_ctxt.mouse_x, my_ctxt.mouse_y);
            retval |= IAL_EVENT_EXTRA;
        }
        break;
    }

    case RANDOM_EVENT_GESTURE_SWIPE_BEGIN: {
        const struct _gesture_event* my_event;

        my_event = get_gesture_event(event);
        if (my_event) {
            extra->event = IAL_EVENT_GESTURE_SWIPE_BEGIN;
            extra->wparam = my_event->nr_figs;
            extra->lparam = 0;
            retval = IAL_EVENT_EXTRA;
        }
        break;
    }

    case RANDOM_EVENT_GESTURE_SWIPE_UPDATE: {
        const struct _gesture_event* my_event;

        my_event = get_gesture_event(event);
        if (my_event) {
            extra->event = IAL_EVENT_GESTURE_SWIPE_UPDATE;
            extra->wparam = my_event->nr_figs;
            extra->lparam = MAKELONG(my_event->dx, my_event->dy);
            retval = IAL_EVENT_EXTRA;
        }
        break;
    }

    case RANDOM_EVENT_GESTURE_SWIPE_END: {
        const struct _gesture_event* my_event;

        my_event = get_gesture_event(event);
        if (my_event) {
            extra->event = IAL_EVENT_GESTURE_SWIPE_END;
            extra->wparam = my_event->nr_figs;
            extra->lparam = my_event->is_cancelled;
            retval = IAL_EVENT_EXTRA;
        }
        break;
    }

    case RANDOM_EVENT_GESTURE_PINCH_BEGIN: {
        const struct _gesture_event* my_event;

        my_event = get_gesture_event(event);
        if (my_event) {
            extra->event = IAL_EVENT_GESTURE_PINCH_BEGIN;
            extra->wparam = my_event->nr_figs;
            extra->lparam = my_event->scale;
            retval = IAL_EVENT_EXTRA;
        }
        break;
    }

    case RANDOM_EVENT_GESTURE_PINCH_UPDATE: {
        const struct _gesture_event* my_event;

        my_event = get_gesture_event(event);
        if (my_event) {
            extra->event = IAL_EVENT_GESTURE_PINCH_UPDATE;
            extra->wparam = MAKELONG(my_event->scale, my_event->da);
            extra->lparam = MAKELONG(my_event->dx, my_event->dy);
            retval = IAL_EVENT_EXTRA;
        }
        break;
    }

    case RANDOM_EVENT_GESTURE_PINCH_END: {
        const struct _gesture_event* my_event;

        my_event = get_gesture_event(event);
        if (my_event) {
            extra->event = IAL_EVENT_GESTURE_PINCH_END;
            extra->wparam = my_event->nr_figs;
            extra->lparam = my_event->is_cancelled;
            retval = IAL_EVENT_EXTRA;
        }
        break;
    }

    default:
        _DBG_PRINTF("IAL>RANDOM: HOW CAN YOU GET HERE? (event->type: %d)\n",
                event->type);
        break;
    }

    return retval;
}

#define LEN_EVENT_TYPES     127

BOOL InitRandomInput (INPUT* input, const char* mdev, const char* mtype)
{
    char logfile[MAX_PATH + 1];
    char eventtypes[LEN_EVENT_TYPES + 1];
    int i;

    if (GetMgEtcValue ("random", "logfile", logfile, MAX_PATH) == ETC_OK) {
        my_ctxt.log_fp = fopen(logfile, "a");
        if (my_ctxt.log_fp == NULL) {
            _WRN_PRINTF("Failed to open file %s for logging; log disabled\n",
                logfile);
        }
    }
    else {
        _WRN_PRINTF("No log file defined; log disabled\n");
    }

    if (GetMgEtcValue ("random", "eventtypes", eventtypes, LEN_EVENT_TYPES) < 0) {
        _WRN_PRINTF("Bad ETC key value: random.eventtypes; use default: mouse\n");
        strcpy(eventtypes, "mouse");
    }

    my_ctxt.nr_machines = 0;
    for (i = 0; i < TABLESIZE(event_state_machines); i++) {
        if (strstr(eventtypes, event_state_machines[i].name)) {
            struct _event_state_machine* machine = event_state_machines + i;

            if (strcmp(event_state_machines[i].name, "keyboard") == 0) {
                if (GetMgEtcIntValue ("random", "minkeycode", &my_ctxt.min_keycode) < 0 ||
                        my_ctxt.min_keycode < MIN_SCANCODE ||
                        my_ctxt.min_keycode > MAX_SCANCODE) {
                    _WRN_PRINTF("Bad ETC key value: random.minkeycode; use default: %d\n",
                            MIN_SCANCODE);
                    my_ctxt.min_keycode = MIN_SCANCODE;
                }

                if (GetMgEtcIntValue ("random", "maxkeycode", &my_ctxt.max_keycode) < 0 ||
                        my_ctxt.max_keycode < MIN_SCANCODE ||
                        my_ctxt.max_keycode < my_ctxt.min_keycode) {
                    _WRN_PRINTF("Bad ETC key value: random.maxkeycode; use default: %d\n",
                            MAX_SCANCODE);
                    my_ctxt.max_keycode = MAX_SCANCODE;
                }

                my_ctxt.kbd_state = calloc(my_ctxt.max_keycode + 1, sizeof(char));
                if (my_ctxt.kbd_state == NULL) {
                    _ERR_PRINTF("IAL>RANDOM: failed to allocate space for key state\n");
                    return FALSE;
                }
            }

            if (strcmp(event_state_machines[i].name, "button") == 0) {
                if (GetMgEtcIntValue ("random", "minbtncode", &my_ctxt.min_btncode) < 0 ||
                        my_ctxt.min_btncode < MIN_BTNCODE ||
                        my_ctxt.min_btncode > MAX_BTNCODE) {
                    _WRN_PRINTF("Bad ETC key value: random.minbtncode; use default: %d\n",
                            MIN_BTNCODE);
                    my_ctxt.min_btncode = MIN_BTNCODE;
                }

                if (GetMgEtcIntValue ("random", "maxbtncode", &my_ctxt.max_btncode) < 0 ||
                        my_ctxt.max_btncode < MIN_SCANCODE ||
                        my_ctxt.max_btncode < my_ctxt.min_btncode) {
                    _WRN_PRINTF("Bad ETC key value: random.maxbtncode; use default: %d\n",
                            MAX_BTNCODE);
                    my_ctxt.max_btncode = MAX_BTNCODE;
                }
            }

            if (machine->start_machine &&
                    (machine->machine = machine->start_machine(&my_ctxt))) {

                my_ctxt.esm[my_ctxt.nr_machines] = machine;
                _DBG_PRINTF("IAL>RANDOM: start event machine for %s: %p\n",
                        event_state_machines[i].name,
                        my_ctxt.esm[my_ctxt.nr_machines]->machine);
                my_ctxt.nr_machines++;
            }
        }
    }

    if (my_ctxt.nr_machines <= 0) {
        _ERR_PRINTF("IAL>RANDOM: bad random.eventtypes: %s\n", eventtypes);
        free(my_ctxt.kbd_state);
        my_ctxt.kbd_state = NULL;
        return FALSE;
    }

    my_ctxt.curr_machine = 0;
    my_ctxt.esm[my_ctxt.curr_machine]->resume_machine(
            my_ctxt.esm[my_ctxt.curr_machine]->machine);

    input->update_mouse = mouse_update;
    input->get_mouse_xy = mouse_getxy;
    input->set_mouse_xy = mouse_setxy;
    input->get_mouse_button = mouse_getbutton;
    input->set_mouse_range = mouse_setrange;
    input->suspend_mouse= NULL;
    input->resume_mouse = NULL;

    input->update_keyboard = keyboard_update;
    input->get_keyboard_state = keyboard_getstate;
    input->suspend_keyboard = NULL;
    input->resume_keyboard = NULL;
    input->set_leds = NULL;
    input->wait_event_ex = wait_event_ex;

    srand (__mg_os_get_random_seed ());
    return TRUE;
}

void TermRandomInput (void)
{
    int i;

    for (i = 0; i < TABLESIZE(event_state_machines); i++) {
        if (my_ctxt.esm[i]->machine) {
            if (my_ctxt.esm[i]->stop_machine)
                my_ctxt.esm[i]->stop_machine(my_ctxt.esm[i]->machine);
            else
                free (my_ctxt.esm[i]->machine);

            my_ctxt.esm[i]->machine = NULL;
        }
    }

    if (my_ctxt.log_fp) {
        fclose(my_ctxt.log_fp);
        my_ctxt.log_fp = NULL;
    }

    if (my_ctxt.kbd_state) {
        free(my_ctxt.kbd_state);
        my_ctxt.kbd_state = NULL;
    }
}

#endif /* _MGIAL_RANDOM */

